#include "sdk.h"
#ifndef CB_PRECOMP
#include <wx/button.h>
#include <wx/dir.h>
#include <wx/event.h>
#include <wx/font.h>
#include <wx/intl.h>
#include <wx/listctrl.h>
#include <wx/notebook.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/utils.h>
#include <wx/xrc/xmlres.h>
#include "globals.h"
#include "manager.h"
#include "logmanager.h"
#endif


#include <wx/busyinfo.h>
#include <wx/colour.h>
#include <wx/ffile.h>
#include <wx/filedlg.h>
#include <wx/progdlg.h>
#include <wx/button.h>

#include "symtabexec.h"

BEGIN_EVENT_TABLE( SymTabExecDlg, wxScrollingDialog )
  EVT_BUTTON( XRCID( "btnExport" ), SymTabExecDlg::OnWriteToFile )
  EVT_BUTTON( XRCID( "btnNext" ), SymTabExecDlg::OnNext )
  EVT_BUTTON( XRCID( "btnCancel" ), SymTabExecDlg::OnCancel )
  EVT_LIST_COL_CLICK( XRCID( "lstLib2Symbol" ), SymTabExecDlg::OnColumnClick )
END_EVENT_TABLE()

bool SymTabExecDlg::ms_bSortAscending = false;
int SymTabExecDlg::ms_iSortColumn = -1;

SymTabExecDlg::~SymTabExecDlg() {
  CleanUp();
}

int SymTabExecDlg::Execute( struct_config config ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::Execute" ) );
  #endif
  DoInitDialog();
  wxString param( _T( "" ) );
  if( config.chkDebug )     {
    param << _T( " --debug-syms" );
  }
  if( config.chkDefined )   {
    param << _T( " --defined-only" );
  }
  if( config.chkDemangle )  {
    param << _T( " --demangle" );
  }
  if( config.chkExtern )    {
    param << _T( " --extern-only" );
  }
  if( config.chkSpecial )   {
    param << _T( " --special-syms" );
  }
  if( config.chkSynthetic ) {
    param << _T( " --synthetic" );
  }
  if( config.chkUndefined ) {
    param << _T( " --undefined-only" );
  }
  wxString cmd;
  if( config.txtNM.Trim().IsEmpty() ) {
    cmd << _T( "nm" ) << param;
  } else {
    cmd << ( config.txtNM.Trim() ) << _T( " " ) << param;
  }
  int retval = -1;
  if( config.choWhatToDo == 0 ) {
    retval = ExecuteMulti( config, cmd );
  } else if( config.choWhatToDo == 1 ) {
    retval = ExecuteSingle( config, cmd );
  } else {
    Manager::Get()->GetLogManager()->DebugLog( _T( "SymTab: Invalid (unsupported) choice." ) );
  }
  CleanUp();
  return retval;
}

void SymTabExecDlg::DoInitDialog() {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::DoInitDialog" ) );
  #endif
  if( !SymTabExecDlgLoaded ) {
    SymTabExecDlgLoaded =
      wxXmlResource::Get()->LoadObject( this, parent, _T( "dlgSymTabExec" ), _T( "wxScrollingDialog" ) );
    wxFont font( 10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL );
    m_ListCtrl = XRCCTRL( *this, "lstLib2Symbol",     wxListCtrl );
    m_ListCtrl->InsertColumn( 0, _T( "item" ),  wxLIST_FORMAT_RIGHT );
    m_ListCtrl->InsertColumn( 1, _T( "value" ), wxLIST_FORMAT_LEFT );
    m_ListCtrl->InsertColumn( 2, _T( "type" ),  wxLIST_FORMAT_CENTRE );
    m_ListCtrl->InsertColumn( 3, _T( "name" ),  wxLIST_FORMAT_LEFT );
    m_TextHelp = XRCCTRL( *this, "txtHelp", wxTextCtrl );
    m_TextHelp->SetFont( font );
    m_TextMisc = XRCCTRL( *this, "txtMisc", wxTextCtrl );
    m_TextMisc->SetFont( font );
  }
}

void SymTabExecDlg::EndModal( int retCode ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::EndModal" ) );
  #endif
  wxScrollingDialog::EndModal( retCode );
}

void SymTabExecDlg::OnWriteToFile( wxCommandEvent& ( event ) ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::OnWriteToFile" ) );
  #endif
  wxString es = wxEmptyString;
  wxFileDialog fd( parent, _( "Save NM output to file" ), es, es, _T( "*.*" ), wxFD_SAVE );
  if( fd.ShowModal() == wxID_OK ) {
    wxFFile file( fd.GetPath().c_str(), _T( "w" ) );
    for( size_t n = 0; n < nm_result.GetCount(); ++n ) {
      file.Write( nm_result[n] );
      file.Write( _T( "\n" ) );
    }
    file.Close();
  }
}

void SymTabExecDlg::OnNext( wxCommandEvent& ( event ) ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::OnNext" ) );
  #endif
  wxScrollingDialog::EndModal( wxID_OK );
}

void SymTabExecDlg::OnCancel( wxCommandEvent& ( event ) ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::OnCancel" ) );
  #endif
  wxScrollingDialog::EndModal( wxID_CANCEL );
}

struct customListEntry {
  customListEntry( long i, const wxString& col1, const wxString& col2, const wxString& col3 ) {
    index = i;
    column_texts[0] = col1;
    column_texts[1] = col2;
    column_texts[2] = col3;
  }
  long index;
  wxString column_texts[3];
};

void SymTabExecDlg::CleanUp() {
  nm_result.Empty();
  nm_errors.Empty();
  ClearUserData();
}

void SymTabExecDlg::ClearUserData() {
  for( long i = 0; i < m_ListCtrl->GetItemCount(); ++i ) {
    customListEntry* data = ( customListEntry* )m_ListCtrl->GetItemData( i );
    delete data;
  }
  m_ListCtrl->DeleteAllItems();
}

inline int wxCALLBACK SortFunction( wxIntPtr item1, wxIntPtr item2, wxIntPtr dlg ) {
  SymTabExecDlg   *dialog = ( SymTabExecDlg* )   dlg;
  customListEntry *data1  = ( customListEntry* ) item1;
  customListEntry *data2  = ( customListEntry* ) item2;
  if( !data1 ) {
    return -1;
  }
  if( !data2 ) {
    return 1;
  }
  long col       = dialog->GetSortColumn();
  bool ascending = dialog->GetSortAscending();
  if( col == 0 ) {
    int ret = data1->index - data2->index;
    if( ascending ) {
      return ret;
    } else {
      return -ret;
    }
  }
  col -= 1;
  int ret = data1->column_texts[col].CmpNoCase( data2->column_texts[col] );
  if( ascending ) {
    return ret;
  } else {
    return -ret;
  }
}


void SymTabExecDlg::OnColumnClick( wxListEvent& event ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::OnColumnClick" ) );
  #endif
  if( event.GetColumn() != ms_iSortColumn ) {
    ms_bSortAscending = true;
  } else {
    ms_bSortAscending = !ms_bSortAscending;
  }
  ms_iSortColumn = event.GetColumn();
  wxBusyInfo wait( _( "Please wait, sorting..." ) );
  m_ListCtrl->SortItems( SortFunction, ( intptr_t )this );
}

int SymTabExecDlg::ExecuteMulti( struct_config &config, wxString cmd ) {
  wxString the_symbol = config.txtSymbol.Trim();
  wxDir dir( config.txtLibraryPath );
  if( !dir.IsOpened() ) {
    return -1;
  }
  size_t num_files = 0;
  wxArrayString files;
  if( config.chkIncludeA ) {
    num_files += wxDir::GetAllFiles( config.txtLibraryPath, &files, _T( "*.a" ) );
  }
  if( config.chkIncludeLib ) {
    num_files += wxDir::GetAllFiles( config.txtLibraryPath, &files, _T( "*.lib" ) );
  }
  if( config.chkIncludeO ) {
    num_files += wxDir::GetAllFiles( config.txtLibraryPath, &files, _T( "*.o" ) );
  }
  if( config.chkIncludeObj ) {
    num_files += wxDir::GetAllFiles( config.txtLibraryPath, &files, _T( "*.obj" ) );
  }
  if( !num_files ) {
    msging( _( "Could not find any files matching the criteria." ), _( "错误" ),
            wxICON_ERROR | wxOK, ( wxWindow* )Manager::Get()->GetAppWindow() );
  } else {
    XRCCTRL( *this, "btnNext", wxButton )->Enable( true );
    bool something_found = false;
    for( size_t i = 0; i < num_files; i++ ) {
      wxString this_cmd = cmd;
      this_cmd << _T( " \"" ) << files[i] << _T( "\"" );
      if( !ExecuteNM( files[i], this_cmd ) ) {
        return -1;
      }
      int parse_result = ParseOutput( files[i], the_symbol );
      if( parse_result != 0 ) {
        something_found = true;
        if( i == ( num_files - 1 ) ) {
          XRCCTRL( *this, "btnNext", wxButton )->Enable( false );
        }
        XRCCTRL( *this, "stxtFile", wxStaticText )->SetLabel( _( "File: " ) + files[i] );
        int retval = wxScrollingDialog::ShowModal();
        if( retval == wxID_OK )
          ;
        else if( retval == wxID_CANCEL ) {
          break;
        } else {
          return -1;
        }
      }
    }// for
    if( !something_found ) {
      wxString msg;
      msg << _( "The search for \"" ) << the_symbol
          << _( "\" produced no results." );
      msging( msg, _( "Info" ), wxICON_INFORMATION | wxOK,
              ( wxWindow* )Manager::Get()->GetAppWindow() );
    }
  }
  return ( wxID_OK );
}

int SymTabExecDlg::ExecuteSingle( struct_config &config, wxString cmd ) {
  wxString the_library = config.txtLibrary.Trim();
  wxString the_symbol  = config.txtSymbol.Trim();
  cmd << _T( " \"" ) << the_library << _T( "\"" );
  if( !ExecuteNM( the_library, cmd ) ) {
    return -1;
  }
  int retval = ParseOutput( the_library, the_symbol );
  if( retval == 0 ) {
    wxString msg;
    msg << _( "The search in:\n" ) << the_library
        << _( "\nfor \"" ) << the_symbol << _( "\" produced no results." );
    msging( msg, _( "Info" ), wxICON_INFORMATION | wxOK,
            ( wxWindow* )Manager::Get()->GetAppWindow() );
  } else {
    XRCCTRL( *this, "stxtFile", wxStaticText )->SetLabel( _( "File: " ) + the_library );
    XRCCTRL( *this, "btnNext",  wxButton )->Enable( false );
    wxScrollingDialog::ShowModal();
  }
  return ( wxID_OK );
}

bool SymTabExecDlg::ExecuteNM( wxString lib, wxString cmd ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->( _T( "SymTabExecDlg::ExecuteNM" ) );
  Manager::Get()->GetLogManager()->( _T( "Library: " )   + lib );
  #endif
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTab: Executing: " ) + cmd );
  wxString p_msg;
  p_msg << _( "Launching NM tool for:\n" ) << lib
        << _( "\nPlease wait, this can take a long time..." );
  wxBusyInfo* wait = new wxBusyInfo( p_msg );
  CleanUp();
  int pid = wxExecute( cmd, nm_result, nm_errors );
  if( wait ) {
    delete wait;
  }
  if( pid == -1 ) {
    wxString msg;
    msg << _( "Unable to execute NM.\n" )
        << _( "Be sure it is in the OS global path.\n" )
        << _( "SymTab could not complete the operation." );
    msging( msg, _( "错误" ), wxICON_ERROR | wxOK,
            ( wxWindow* )Manager::Get()->GetAppWindow() );
    return false;
  }
  return true;
}

int SymTabExecDlg::ParseOutput( wxString lib, wxString filter ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->( _T( "SymTabExecDlg::ParseOutput" ) );
  #endif
  int symbols_processed = -1;
  if( nm_result.IsEmpty() ) {
    ParseOutputError();
  } else {
    symbols_processed = ParseOutputSuccess( lib, filter );
  }
  if( symbols_processed == 0 ) {
    Manager::Get()->GetLogManager()->DebugLog( _T( "SymTab: Parsing produced no match (no results)." ) );
  }
  return symbols_processed;
}

void SymTabExecDlg::ParseOutputError() {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::ParseOutputError" ) );
  #endif
  wxString output;
  size_t   count = nm_errors.GetCount();
  if( count ) {
    for( size_t n = 0; n < count; ++n ) {
      output << nm_errors[n] << _T( "\n" );
    }
  } else {
    output = _( "An unknown error has occured. NM produced no output." );
  }
  m_TextMisc->SetValue( output );
  wxColour colour( 255, 0, 0 );
  m_TextMisc->SetForegroundColour( colour );
  XRCCTRL( *this, "nbTabs", wxNotebook )->SetSelection( 1 );
}

int SymTabExecDlg::ParseOutputSuccess( wxString lib, wxString filter ) {
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTabExecDlg::ParseOutputSuccess" ) );
  #endif
  size_t count = nm_result.GetCount();
  if( !count ) {
    #ifdef TRACE_SYMTAB_EXE
    Manager::Get()->GetLogManager()->DebugLog( _T( "SymTab: No entries. Skipping." ) );
    #endif
    return 0;
  }
  Manager::Get()->GetLogManager()->DebugLog( F( _T( "SymTab: Parsing %lu items..." ), static_cast<unsigned long>( count ) ) );
  wxProgressDialog* progress = 0L;
  if( count > 2000 ) {
    wxString p_msg;
    p_msg << _( "Parsing NM information for:\n" ) << lib << _( "\nPlease wait..." );
    progress = new wxProgressDialog( _( "SymTab plugin" ), p_msg );
  }
  int entries = 0;
  bool do_show = true;
  wxString the_line;
  wxString the_value;
  wxString the_type;
  wxString the_name;
  wxString s_item;
  for( size_t n = 0 ; n < count; ++n ) {
    the_line = nm_result[n];
    if( !the_line.IsEmpty() ) {
      if( !filter.IsEmpty() ) {
        do_show = the_line.Contains( filter );
      }
      if( do_show ) {
        long item = m_ListCtrl->InsertItem( entries, _T( "" ) );
        if( item != -1 ) {
          s_item.Printf( _T( "%6ld" ), item );
          m_ListCtrl->SetItem( item, 0, s_item );
          if( the_line.Last() == _T( ':' ) ) {
            m_ListCtrl->SetItem( item, 3, the_line.Trim() );
            m_ListCtrl->SetItemBackgroundColour( item, wxTheColourDatabase->Find( _T( "LIGHT GREY" ) ) );
          } else {
            the_value = ( ( the_line.Mid( 0, 8 ) ).Trim( true ) ).Trim();
            m_ListCtrl->SetItem( item, 1, the_value );
            the_type  = ( ( the_line.Mid( 9, 1 ) ).Trim( true ) ).Trim();
            m_ListCtrl->SetItem( item, 2, the_type );
            the_name  = ( ( the_line.Mid( 11 ) ).Trim( true ) ).Trim();
            m_ListCtrl->SetItem( item, 3, the_name );
            if( the_name.IsEmpty() ) {
              m_ListCtrl->SetItemBackgroundColour( item, wxTheColourDatabase->Find( _T( "RED" ) ) );
            }
          }
          m_ListCtrl->SetItemData( item, ( intptr_t )new customListEntry( n, the_value, the_type, the_name ) );
          ++entries;
        }
      }
    }
    if( progress ) {
      progress->Update( ( 100 * n ) / ( count - 1 ) );
    }
  }
  #ifdef TRACE_SYMTAB_EXE
  Manager::Get()->GetLogManager()->DebugLog( _T( "SymTab: Operated %d entries." ), entries );
  #endif
  if( entries ) {
    m_ListCtrl->SetColumnWidth( 0, wxLIST_AUTOSIZE );
    m_ListCtrl->SetColumnWidth( 1, wxLIST_AUTOSIZE_USEHEADER );
    m_ListCtrl->SetColumnWidth( 2, wxLIST_AUTOSIZE_USEHEADER );
    m_ListCtrl->SetColumnWidth( 3, wxLIST_AUTOSIZE );
  }
  if( progress ) {
    progress->Update( 100 );
    progress->Destroy();
  }
  return entries;
}
